package com.concurrent.sample.common.lock;

import java.lang.reflect.Method;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Component
@Aspect
@Slf4j
@Order(20)
@RequiredArgsConstructor
public class DistributedLockAspect {

    private final RedissonClient redissonClient;

    @Pointcut("@annotation(com.concurrent.sample.common.lock.DistributedLock)")
    public void pointCut() {
    }

    @Around("pointCut()")
    public Object lock(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        Object result = null;
        Signature signature = proceedingJoinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Object target = proceedingJoinPoint.getTarget();
        Method method = target.getClass().getMethod(methodSignature.getName(), methodSignature.getParameterTypes());
        Object[] args = proceedingJoinPoint.getArgs();

        DistributedLock distributedLock = method.getAnnotation(DistributedLock.class);
        String lockKey = DistributedLockUtils.getLockKey(distributedLock, args);
        RLock rLock = redissonClient.getLock(lockKey);
        try {
            Long start = System.currentTimeMillis();
            boolean lockFlag = rLock.tryLock(distributedLock.waitTime(), distributedLock.expireTime(), distributedLock.timeUnit());
            if (lockFlag) {
                result = proceedingJoinPoint.proceed();
            } else {
                log.info("get distributed lock key failed, wait time = {} (ms)", System.currentTimeMillis() - start);
                throw new DistributedLockTimeOutException("get distributed lock key failed");
            }
        } catch (DistributedLockTimeOutException e) {
            throw e;
        }  catch (Exception e) {
            log.error("get distributed lock key error", e);
            throw new RuntimeException( "get distributed lock key error");
        } finally {
            // 判断是否是当前线程持有锁
            if(rLock.isHeldByCurrentThread()){
                try{
                    rLock.unlock();
                } catch (Exception e){
                    // 释放锁，锁不存在时，静默处理
                }
            }
        }
        return result;
    }
}